package dungeon;

import java.util.Iterator;


import basicPackage.Coordinate;
import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;



/**
 * A class representing a dungeon containing squares under their respective coordinates.
 * 
 * @invar	| getMaxCoordinate != null
 * @invar	| getMaxCoordinate().isPositive
 * @invar	| if(getSuperCompositedungeon != null)
 * 			|	getMaxCoordinate().add(getDungeonCoordinate()).isSmallerOrEqualTo(getSuperCompositeDungeon().geMaxCoordinate
 * @invar	| getSuperCompositeDungeon() != this
 * @invar	| (getDungeonCoordinate() == null || getDungeonCoordinate().isPositive())
 * @invar	| if(containsElementCoordinate(coordinate)
 * 			|	coordinate.isPositive && coordinate.isSmallerOrEqualTo(getMaxCoordinate()) && !coordinate.hasSameValueForAllCoordinates()
 * @invar	| if( exists coordinate: getElement(coordinate) != null)
 * 			|	exists no coordinate2: coordinate2 != coordinate && getElement(coordinate) == getElement(coordinate2)
 * @invar	| if( exists element: exists coordinate: getElement(coordinate) == element))
 * 			|	exists no element2: element2 != element: getElement(coordinate) == element2
 * @invar	| if( exists coordinate: getSquare(coordinate) != null)
 * 			|	exists no coordinate2: coordinate2 != coordinate && getSquare(coordinate) == getSquare(coordinate2)
 * @invar	| if( exists square: exists coordinate: getSquare(coordinate) == square))
 * 			|	exists no square2: square != square2: getSquare(coordinate) == square2
 * @invar	| if(containsSquareCoordinate(coordinate))
 * 			|	for each Direction.i: containsSquareCoordinate(coordinate.getNeighboringCoordinates().get(i))
 * 			|		getSquare(coordinate).getBorderingSquare(i) == getSquare(coordinate.getNeighboringCoordinates().get(i))
 * 			|		getSquare(coordinate.getNeighboringCoordinates().get(i)).getBorderingSquare(i.oppositeDirection()) == getSquare(coordinate)
 * 
 * @author Laurens Vanderstraeten
 * 		   2de Bach. Burgerlijk Ingenieur, wtkcws
 */
public abstract class SquareDungeon<S extends Square> extends Dungeon<S>{

	/**
	 * Constructs a square dungeon.
	 * 
	 * @param 	maxCoordinate
	 * 			The maximum coordinate determining the outer point in the cuboid space of 
	 * 			possible square coordinates.
	 * @post	| getMaxCoordinate() == maxCoordinate
	 * @throws	IllegalArgumentException
	 * 			| !isValidMaxCoordinate(maxCoordinate)
	 */
	@Raw
	public SquareDungeon(Coordinate maxSquareCoordinate) {
		super(maxSquareCoordinate);
	}
	
	/**
	 * Return the maximum percentage of inherently slippery squares a square dungeon can contain.
	 * 
	 * @return 	| result == 20
	 */
	@Basic @Immutable @Raw
	public static double getMaxInherentlySlipperySquaresPercentage(){
		return maxInherentlySlipperySquaresPercentage;
	}
	
	/**
	 * Variable for registering the maximum percentage of inherently slippery squares a dungeon can contain.
	 */
	private static final double maxInherentlySlipperySquaresPercentage = 20;

	/** 
	 * Return the square registered under the given coordinate
	 * 
	 * @param	coordinate
	 * 			The coordinate of the square to be retrieved.
	 * @return	getElement(coordinate)
	 */
	@Raw @Override
	public Square getSquare(Coordinate coordinate){
		return getElement(coordinate);
	}
	
	/**
	 * Return an Iterator iterating all the squares in this dungeon.
	 * 
	 * @effect	| getElementIterator();
	 */
	@SuppressWarnings("unchecked")
	@Raw @ Override
	public Iterator<Square> getSquareIterator() {
		return (Iterator<Square>)getElementIterator();
	}

	/**
	 * Return an Iterator iterating all the Coordinates containing squares in this dungeon.
	 * 
	 * @effect	| getElementCoordinateIterator()
	 */
	@Raw @Override
	public Iterator<Coordinate> getSquareCoordinateIterator() {
		return getElementCoordinateIterator();
	}
	
	/** 
	 * Return the number of squares in this dungeon.
	 * 
	 * @effect	| getNbElements()
	 */
	@Override @Raw
	public int getNbSquares(){
		return getNbElements();
	}
	
	/**
	 * Return the number of inherently slippery squares in this square dungeon.
	 * 
	 * @return	| for all i element getElementMap.values: i.isInherentlySlippery():
	 * 			|	result = sum(i)
	 */
	@Raw
	public int getNbInherentlySlipperySquares() {
		int nbInherentlySlipperySquaresUpTillNow = 0;
		Iterator<Square> squareIterator = getSquareIterator();
		while(squareIterator.hasNext()){
			if (squareIterator.next().isInherentlySlippery())
				nbInherentlySlipperySquaresUpTillNow++;
		}
		return nbInherentlySlipperySquaresUpTillNow;
	}
	
	/** 
	 * Check whether this dungeon contains a square on the given coordinate.
	 * 
	 * @effect	| containsElementCoordinate(squareCoordinate)
	 */
	@Raw @Override
	public Boolean containsSquareCoordinate(Coordinate squareCoordinate) {
		return containsElementCoordinate(squareCoordinate);
	}

	/**
	 * Return whether this dungeon contains the given square.
	 * 
	 * @param 	square
	 * 			The square to be checked.
	 * @effect	| containsElement(square)
	 */
	@Raw @Override
	public Boolean containsSquare(Square square) {
		return containsElement(square);
	}
	
	/**
	 * Check whether the given coordinate is a valid square coordinate.
	 * 
	 * @param 	coordinate
	 * 			The coordinate to be checked.
	 * @return	| if(!isValidElementCoordinate(coordinate))
	 *			|	result == false
	 *			| if(!isCoordinateFreeWorldSquareCoordinateInRootDungeon(coordinate))
	 *			|	result == false
	 *			| else then
	 *			|	return true
	 */
	@Raw
	public Boolean isValidSquareCoordinate(Coordinate coordinate) {
		if(!isValidElementCoordinate(coordinate))
			return false;
		if(!isCoordinateFreeWorldSquareCoordinateInRootDungeon(coordinate))
			return false;
		return true;
	}
	
	/**
	 * Check whether the given square is a valid square.
	 * 
	 * @param 	square
	 * 			The square to be checked.
	 * @return	| if(!isValidElement(square))
	 *			|	result == false
	 *			| if (getRootDungeon().containsSquare(square))
	 *			|	result == false
	 *			| else then
	 *			|	result == true
	 */
	public Boolean isValidSquare(S square) {
		if(!isValidElement(square))
			return false;
		if (getRootDungeon().containsSquare(square))
			return false;
		return true;
	}
	
	/**
	 * Check whether the inherently slippery square percentage limit has been violated.
	 * 
	 * @Return	| if(getNbSquares() == 0)
	 *			|	result == false;
	 *			| if (100*((double)getNbInherentlySlipperySquares())/((double)getNbSquares()) < getMaxInherentlySlipperySquaresPercentage())
	 *			| 	result == false;
	 *			| else then
	 *			|	result == true;
	 */
	@Raw
	public Boolean inherentlySlipperySquarePercentageLimitViolated(){
		if(getNbSquares() == 0)
			return false;
		if (100*((double)getNbInherentlySlipperySquares())/((double)getNbSquares()) < getMaxInherentlySlipperySquaresPercentage())
			return false;
		return true;
	}
	
	/**
	 * Add the given square to this dungeon under the given coordinate.
	 * 
	 * @param 	coordinate
	 * 			The coordinate under which the square should be added.
	 * @param 	square
	 * 			The square to be added to the dungeon.
	 * @post	| getElementMap().get(coordinate) == square
	 * @post	| getElement(coordinate) == square
	 * @post	| getSquare(coordinate) == square
	 * @post	| containsSquare(dungeon) == square
	 * @post	|for all Direction.i:
	 * 			|	if(containsSquareCoordinate(coordinate.getNeighboringCoordinates().get(i)))
	 * 			|		getSquare(coordinate.getNeighboringCoordinates().get(i)).getBorderingSquares().contains(getSquare(coordinate))
	 * @throws 	IllegalArgumentException
	 * 			| !isValidSquare(square) || !isValidSquareCoordinate(coordinate)
	 * @throws 	InherentlySlipperyLimitViolatedException 
	 * 			If adding this squares causes the inherently slippery square percentage limit to be violated.
	 */
	@Raw
	public void placeSquare(Coordinate coordinate, S square) throws IllegalArgumentException, InherentlySlipperyLimitViolatedException {
		if(!isValidSquare(square) || !isValidSquareCoordinate(coordinate))
			throw new IllegalArgumentException();
		setElement(coordinate, square);
		if(inherentlySlipperySquarePercentageLimitViolated()){
			removeElement(coordinate);
			throw new InherentlySlipperyLimitViolatedException(this);
		}
		resolveConnectionToNeighbours(coordinate);
	}

	/**
	 * Set the square on the given coordinate to the given inherently slippery.
	 * 
	 * @param 	coordinate
	 * 			The coordinate of the square to be set.
	 * @param 	inherentlySlippery
	 * 			Whether the square should be inherentlySlippery or not.
	 * @post	| getSquare(coordinate).isInherentlySlippery() == inherentlySlippery
	 * @post	| if(old.getSquare(coordinate).isInherentlySlippery() == false && inherentlySlippery == true)
	 * 			|	new.getNbInherentlySlipperySquares() == old.getNbInherentlySlipperySquares() + 1
	 * @post	| if(old.getSquare(coordinate).isInherentlySlippery() == true && inherentlySlippery == false)
	 * 			|	new.getNbInherentlySlipperySquares() == old.getNbInherentlySlipperySquares() - 1
	 * @throws 	IllegalArgumentException
	 * 			| !containsSquareCoordinate(coordinate)
	 * @throws 	InherentlySlipperyLimitViolatedException
	 * 			inherentlySlipperySquarePercentageLimitViolated()
	 * 			If setting this squares to the given inherently slippery causes the inherently slippery square percentage limit to be violated.
	 * 			
	 */
	@Raw
	public void setSquareInherentlySlippery(Coordinate coordinate, Boolean inherentlySlippery) throws IllegalArgumentException, InherentlySlipperyLimitViolatedException {
		if(!containsSquareCoordinate(coordinate))
			throw new IllegalArgumentException();
		getSquare(coordinate).setInherentlySlippery(inherentlySlippery);
		if (inherentlySlipperySquarePercentageLimitViolated()){
			getSquare(coordinate).setInherentlySlippery(!inherentlySlippery);
			throw new InherentlySlipperyLimitViolatedException(this);
		}
	}

}